package org.scmpatrol.scm.connector.svn;

import java.io.ByteArrayOutputStream;
import java.io.OutputStream;

import org.apache.commons.lang.NotImplementedException;
import org.apache.commons.lang.ObjectUtils;
import org.apache.log4j.Logger;
import org.scmpatrol.scm.SCMCommit;
import org.scmpatrol.scm.SCMPath;
import org.scmpatrol.scm.action.SCMAction;
import org.scmpatrol.scm.action.SCMActionTypeEnum;
import org.scmpatrol.scm.action.SCMAddAction;
import org.scmpatrol.scm.action.SCMModificationAction;
import org.scmpatrol.scm.diff.Diff;
import org.scmpatrol.scm.diff.DiffGenerator;
import org.tmatesoft.svn.core.SVNCommitInfo;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNPropertyValue;
import org.tmatesoft.svn.core.SVNURL;
import org.tmatesoft.svn.core.io.ISVNEditor;
import org.tmatesoft.svn.core.io.diff.SVNDiffWindow;
import org.tmatesoft.svn.core.wc.SVNClientManager;
import org.tmatesoft.svn.core.wc.SVNRevision;

public class SVNEditor implements ISVNEditor {
	final private static Logger LOGGER = Logger.getLogger(SVNEditor.class.getName());

	/**
	 * The commit revision we want the diff
	 */
	private final SVNRevision targetRevision;

	/**
	 * The current repository url
	 */
	private final SVNURL repositoryURL;

	/**
	 * The previous revision of the inspected component. Change during the diff
	 * treatment
	 */
	private SVNRevision previousRevision;

	/**
	 * The commit object. This object will be update with the detail of the
	 * commit modifications (diff, properties, ...)
	 */
	private SCMCommit commit;

	/**
	 * Content when diff is on a text file
	 */
	private String previousTextFileContent;

	/**
	 * The file content for the target revision;
	 */
	private String targetTextFileContent;

	/**
	 * The current action type
	 */
	private SCMActionTypeEnum actionType;

    /**
     * Is the current addition a copy ?
     */
    private boolean copied = false;

	private final SVNClientManager repositoryManager;

	public SVNEditor(SVNClientManager repositoryManager, SVNURL repositoryURL,
			Long revision, SCMCommit commit) {
		targetRevision = SVNRevision.create(revision);
		this.repositoryURL = repositoryURL;
		this.repositoryManager = repositoryManager;
		this.commit = commit;
	}

	@Override
	public void abortEdit() throws SVNException {
		LOGGER.error("Abort edit not implemented");
	}

	@Override
	public void absentDir(String path) throws SVNException {
		LOGGER.error("Absent dir " + path);
	}

	@Override
	public void absentFile(String path) throws SVNException {
		LOGGER.error("Absent path " + path);
	}

	@Override
	public void addDir(String path, String copyFromPath, long copyFromRevision)
			throws SVNException {
		LOGGER.info("add dir " + path + " copied from " + copyFromPath
				+ " copy from rev " + copyFromRevision);
		actionType = SCMActionTypeEnum.ADD;
        copied = copyFromPath != null;
	}

	@Override
	public void addFile(String path, String copyFromPath, long copyFromRevision)
			throws SVNException {
		LOGGER.info("add file " + path + " copy from " + copyFromPath + "("
				+ copyFromRevision + ")");
		actionType = SCMActionTypeEnum.ADD;
        copied = copyFromPath != null;
	}

	@Override
	public void closeDir() throws SVNException {
		actionType = null;
        copied=false;
		previousRevision = null;
	}

	@Override
	public SVNCommitInfo closeEdit() throws SVNException {
		LOGGER.debug("close edit");
		return null;
	}

	@Override
	public void closeFile(String path, String textChecksum) throws SVNException {
		LOGGER.debug("close file " + path + " with checksum " + textChecksum);
        copied=false;
		previousRevision = null;
	}

	@Override
	public void deleteEntry(String path, long revision) throws SVNException {
		LOGGER.info("delete entry " + path + " for revision " + revision);
	}

	@Override
	public void openDir(String path, long revision) throws SVNException {
		LOGGER.debug("open dir " + path + " for revision " + revision);
		previousRevision = SVNRevision.create(revision);
	}

	@Override
	public void openFile(String path, long revision) throws SVNException {
		LOGGER.info("open file " + path + " for revision " + revision);
		previousRevision = SVNRevision.create(revision);
		actionType = SCMActionTypeEnum.UPDATE;
        copied=false;
	}

	@Override
	public void openRoot(long revision) throws SVNException {
		LOGGER.debug("open root for revision " + revision);
	}

	@Override
	public void targetRevision(long revision) throws SVNException {
		LOGGER.info("target revision " + revision);
	}

	@Override
	public void applyTextDelta(String path, String baseChecksum)
			throws SVNException {
		LOGGER.debug("apply text delta " + path + (copied ? "" : " not ") + " copied");

        if (copied) {
            LOGGER.debug("Copied file, content is already known");
            return;
        }

		if (SCMActionTypeEnum.ADD == actionType) {
            LOGGER.debug("New file " + path);
            previousTextFileContent = "";
		} else {
            LOGGER.debug("getting content of " + path + ":" + previousRevision);

            previousTextFileContent = getFileContent(path, previousRevision);

            LOGGER.debug("previous size : " + previousTextFileContent.length());
		}

		LOGGER.debug("getting content of " + path + " : " + targetRevision);
		targetTextFileContent = getFileContent(path, targetRevision);

		LOGGER.debug("target size : " + targetTextFileContent.length());
		
		// TODO use generics to avoid casting
		if (SCMActionTypeEnum.ADD.equals(actionType)) {
			LOGGER.debug("Action is addition, setting content");
			
			SCMAddAction action = (SCMAddAction) getAction(commit, path);
            if (action != null) {
                // TODO remove this hacks when directory is copied, not returned in this editor
                action.setContent(targetTextFileContent);
            } else {
                LOGGER.debug("Content not found, probably because parent was copied");
            }
		} else if (SCMActionTypeEnum.UPDATE.equals(actionType)) {
			LOGGER.debug("Current action is modification, fetching diff");
						
			SCMModificationAction action = (SCMModificationAction) getAction(
					commit, path);

            if (action != null) {
                // TODO remove this hacks when directory is copied, not returned in this editor
                LOGGER.debug("Generating diff for " + path + " r " + previousRevision
                        + ":" + targetRevision);
                Diff diff = DiffGenerator.generateDiff(previousTextFileContent,
                        targetTextFileContent);

                action.setDiff(diff);
            }			
		} else {
			throw new NotImplementedException("Unknown action");
		}
	}

	@Override
	public OutputStream textDeltaChunk(String path, SVNDiffWindow diffWindow)
			throws SVNException {
        // TODO Remove this, just for compatibility
        return null;
	}

	@Override
	public void textDeltaEnd(String path) throws SVNException {
		LOGGER.debug("textDeltaEnd " + path);
		previousTextFileContent = null;
		targetTextFileContent = null;

	}

	@Override
	public void changeDirProperty(String name, SVNPropertyValue value)
			throws SVNException {
        LOGGER.debug("Change dir property " + name + " value : "
                + ObjectUtils.toString(value));
    }

	@Override
	public void changeFileProperty(String path, String propertyName,
			SVNPropertyValue propertyValue) throws SVNException {
        LOGGER.debug("Change file property " + path + " name " + propertyName
                + " value : " + propertyValue.toString());
	}

	/**
	 * Return the commit action detected parsing the commit log corresponding to
	 * the current path
	 * 
	 * @param commit
	 *            the commit object
	 * @param path
	 *            the path
	 * @return the commit action associated with the path
	 */
	private SCMAction getAction(SCMCommit commit, String path) {
		SCMAction action = null;
		SCMPath scmPath = new SCMPath(path);
        for (SCMAction currentAction : commit.getActions()) {
			if (currentAction.getPath().equals(scmPath)) {
				return currentAction;
			}
		}
        // TODO we should not pass here
//		throw new RuntimeException("Commit must contains all modification, "
//				+ path + " not found");
        return action;
	}
	
	private String getFileContent(String path, SVNRevision revision) throws SVNException {
		ByteArrayOutputStream output = new ByteArrayOutputStream();
				
		repositoryManager.getWCClient().doGetFileContents(
				repositoryURL.appendPath(path, false),
				revision, revision, false, output);

		return output.toString();
	}
}
